﻿using System;
using System.Drawing;
using JamesZhao.Engine.Graphics.Surfaces;
using JamesZhao.Engine.Utils;
using SlimDX;
using SlimDX.Direct3D11;
using SlimDX.DXGI;
using SlimDX.Windows;
using Device = SlimDX.Direct3D11.Device;
using Resource = SlimDX.Direct3D11.Resource;

namespace JamesZhao.Engine.Graphics
{
    public class MainViewport
    {
        private const int FpsUpdateInterval = 100;
        private const Format BackbufferFormat = Format.R8G8B8A8_UNorm_SRGB;
        private readonly double[] _durationsTable = new double[2400];
        private readonly SystemTime _systemTime = new SystemTime();
        private Camera _camera;
        private bool _hasWritten;
        private long _lastFrameNumber;
        private double _lastRenderTime;
        private double _prevTimeStamp;

        internal SwapChain SwapChain { get; private set; }
        internal RenderForm Form { get; private set; }
        internal GraphicsDevice Device { get; private set; }
        public RenderPipeline Pipeline { get; private set; }

        internal ColorSurface ColorSurface { get; private set; }
        internal DepthSurface DepthSurface { get; private set; }

        public Size Size
        {
            get { return Form.ClientSize; }
            set { Form.ClientSize = value; }
        }

        public Camera Camera
        {
            get { return _camera; }
            set
            {
                _camera = value;
                _camera.Lens.AspectRatio = ((float) Form.ClientSize.Width)/Form.ClientSize.Height;
            }
        }

        public long CurrentFrameNumber { get; private set; }

        public void Initialize()
        {
            CreateDeviceAndSwapChain();
            InitializeColorbuffer();
            InitializeDepthBuffer();
            //InitializeViewport();
            Pipeline = new RenderPipeline
                           {
                               ColorSurface = ColorSurface,
                               DepthSurface = DepthSurface,
                               Device = Device,
                               SwapChain = SwapChain
                           };
            Pipeline.Initialize();
            Form.SizeChanged += OutputResized;
            OutputResized(null, null);
        }

        private void OutputResized(object sender, EventArgs e)
        {
            const bool runningUnderNSight = false;

            ColorSurface.DestroyResources();

            DepthSurface.DestroyResources();

            int width = Form.ClientSize.Width;
            int height = Form.ClientSize.Height;
            //MessageBox.Show(width + " " + height + " swapchain=" + SwapChain.Description);
            if (!runningUnderNSight)
            {
                SwapChain.ResizeBuffers(1, width, height, BackbufferFormat, SwapChainFlags.None);
            }
            InitializeColorbuffer();
            InitializeDepthBuffer();

            Pipeline.OnResize(Form.ClientSize);
            if (Camera != null)
                Camera.Lens.AspectRatio = ((float) width)/height;

            InitializeViewport();
        }

        private static SwapChainDescription CreateSwapChainDescription(int width, int height, IntPtr controlHandle)
        {
            return new SwapChainDescription
                       {
                           BufferCount = 1,
                           ModeDescription = new ModeDescription(width,
                                                                 height,
                                                                 new Rational(60, 1),
                                                                 BackbufferFormat),
                           IsWindowed = true,
                           OutputHandle = controlHandle,
                           SampleDescription = new SampleDescription(1, 0),
                           SwapEffect = SwapEffect.Discard,
                           Usage = Usage.RenderTargetOutput
                       };
        }

        internal void InitializeDepthBuffer()
        {
            if (DepthSurface == null)
            {
                DepthSurface = new DepthSurface(Device, TextureTargetMode.Managed);
            }
            else
            {
                DepthSurface.DestroyTexture();
                DepthSurface.DestroyViews();
            }
            DepthSurface.BaseSize = Form.ClientSize;
        }

        internal void InitializeColorbuffer()
        {
            if (ColorSurface == null)
            {
                ColorSurface = new ColorSurface(Device, Resource.FromSwapChain<Texture2D>(SwapChain, 0),
                                               TextureTargetMode.Raw);
            }
            else
            {
                ColorSurface.DestroyTexture();
                ColorSurface.DestroyViews();

                ColorSurface.Texture = Resource.FromSwapChain<Texture2D>(SwapChain, 0);
            }
        }

        private void InitializeViewport()
        {
            Device.Viewport =
                new Viewport(0, 0, Form.ClientSize.Width,
                             Form.ClientSize.Height, 0.0f, 1.0f);
        }


        private void CreateDeviceAndSwapChain()
        {
            Form = new RenderForm();

            var factory = new Factory();


            Device = new GraphicsDevice(new Device(factory.GetAdapter(0),
                DeviceCreationFlags.BgraSupport, //required for direct2d (d2d) support
                new[] { FeatureLevel.Level_10_0, }));

            SwapChain = new SwapChain(factory, Device.Device,
                                      CreateSwapChainDescription(Form.ClientSize.Width, Form.ClientSize.Height,
                                                                 Form.Handle));

            //alt-enter hack
            factory.SetWindowAssociation(Form.Handle, WindowAssociationFlags.IgnoreAll);
            //end hack
        }


        public void Render()
        {
            DisplayFps();
            Pipeline.Render(Camera);
            CurrentFrameNumber++;
        }

        private void DisplayFps()
        {
            double now = _systemTime.CurrentTimeInNs;
            double durationInMs = (now - _prevTimeStamp);
            /*
            if (CurrentFrameNumber < 2400)
            {
                _durationsTable[CurrentFrameNumber] = durationInMs;
            }
            else if (!_hasWritten)
            {
                Console.WriteLine("omg");
                _hasWritten = true;
                File.WriteAllLines("fps.txt", _durationsTable.Select((x, index) => x.ToString()).ToArray());
                Application.Exit();
            }*/
            _prevTimeStamp = now;

            if (CurrentFrameNumber%FpsUpdateInterval == 0)
            {
                long numOfFrames = CurrentFrameNumber - _lastFrameNumber;
                double dt = (_systemTime.CurrentTimeInMs - _lastRenderTime);

                Form.Text = "TIME = " + dt/numOfFrames + " MS";

                _lastRenderTime = _systemTime.CurrentTimeInMs;
                _lastFrameNumber = CurrentFrameNumber;
            }
        }
    }
}